package item;

import exceptions.*;
import attribute.*;
import be.kuleuven.cs.som.annotate.*;

/**
 * @invar	...
 * 			|hasProperPosition() && hasAsPosition(position)
 * 
 * @invar ...
 * 			|
 */
/**
 * @author John Breyssem	1Ba Informatica		r0303394
 * 
 * @author Lars Feyaerts	1Ba Informatica		r0296831
 * 
 * @version	1.3
 *
 */
public abstract class Item {
	private Position position;
	private boolean isOnABoard;
	private boolean isTerminated = false;
	
	/**
	 * Returns the position of this item.
	 */
	@Basic
	public Position getPosition() throws IllegalStateException{
		if(position == null)
			throw new IllegalStateException();
		return this.position;
	}	
	
	/**
	 * Returns the boolean isOnABoard
	 */
	@Basic
	public boolean getIsOnABoard(){
		return this.isOnABoard;
	}
	
	/**
	 * Set whether the item is on a board or not.
	 * 
	 * @param flag
	 * 			The boolean that must be appointed.
	 * @post ...
	 * 			|this.isTerminated() == flag
	 */
	public void setIsOnABoard(boolean flag){
		this.isOnABoard = flag;
	}
	
	/**
	 * Returns whether or not the item is terminated.
	 */
	@Basic
	public boolean isTerminated(){
		return this.isTerminated;
	}
	
	/**
	 * Check whether a couple of coordinates (X and Y) of an item is are valid coordinates.
	 * 
	 * @param X
	 * 			X coordinate of the position of the item
	 * @param Y
	 * 			Y coordinate of the position of the item
	 *
	 * @return	True if and only if both the given X-coordinate is larger than or equals 0 and smaller than or equals 
	 * 				board.getWidth() and the given Y-coordinate is larger than or equals 0 and smaller than or equals 
	 * 				board.getHeight()
	 *				| result == (X >= Board.getMinOfBoard() && X <= board.getWidth() -1) 
	 *				|  	&& (Y >= Board.getMinOfBoard() && Y <= board.getHeight() -1)
	 *
	 */
	public static boolean isValidPosition(long X, long Y, Board board){
			return(X >= Board.getMinOfBoard() && X <= board.getWidth() - 1)
				&& (Y >= Board.getMinOfBoard() && Y <= board.getHeight() - 1);
	}
	
	/**
	 * Checks whether the given position is a valid position.
	 * 
	 * @param position
	 * 			The position to check
	 * @return ...
	 * 			|if(position == null)
	 * 			| 	result == true;
	 * 			...
	 * 			|else
	 * 			| 	result == isValidPosition(position.getX(), position.getY(), position.getBoard())
	 * 		
	 */
	public static boolean isValidPosition(Position position){
		if(position == null)
			return true;
		else
			return isValidPosition(position.getX(), position.getY(), position.getBoard());
	}
	
	
	
	/**
	 * Appoints a new or existing position and a board to this item.
	 * 
	 * @param X	
	 * 			X coordinate that is part of position that must be given to this item.
	 * @param Y
	 * 			Y coordinate that is part of the position that must be given to this item.
	 * @param board
	 * 			The board the position must be on.
	 * 
	 * @post	This method checks whether the position with the given X, Y and board already exists.
	 *			|position c = doesPositionAlreadyExist(X, Y, board)
	 *
	 *
	 *@post		If it doesn't exist, a new position is made using X, Y and board. The given board is
	 *			appointed to the item, the new position is added to the board's list of positions 
	 *			on that board, this item is added to the list of items the new position has 
	 *			and the boolean isOnABoard is set true.
	 *			|if(c == null) then
	 *			|this.new.getPosition() = new Position(X, Y, board)
	 *			|this.getPosition().getBoard().listOfpositions.add(this.getPosition());
	 *			|this.getPosition().listOfItemsOnPosition.add(this);
	 *			|this.isOnABoard = true;
	 *
	 *@post 	...
	 *			|else then
	 *			|this.isOnABoard = true;
	 *			|this.position = c;
	 *			|this.getPosition().listOfItemsOnPosition.add(this);
	 *			|this.addToBoard(board);
	 *
	 *@Throws	IllegalArgumentException
	 *			...
	 *			|board == null || !this.isValidPosition(X, Y, board)
	 *@throws 	PositionTakenException
	 *			...
	 *			|!c.canHaveAsItemOnPosition(this)
	 *
	 */
	public void setValidPosition(long X, long Y, Board board) throws IllegalArgumentException{
		Position p = doesPositionAlreadyExist(X, Y, board);
		try{
			if(board == null || !isValidPosition(X, Y, board))
				throw new IllegalArgumentException();
			
			else{
				if(p == null){
					this.isOnABoard = true;
					this.position = new Position(X, Y, board);
					this.getPosition().getBoard().getListOfPositions().add(this.getPosition());
					this.getPosition().listOfItemsOnPosition.add(this);
					this.addToBoard(board);
				}
				else{
					if(!p.canHaveAsItemOnPosition(this))
						throw new PositionTakenException(p, board);
					else{
						this.isOnABoard = true;
						this.position = p;
						this.getPosition().listOfItemsOnPosition.add(this);
						this.addToBoard(board);
					}
				}
			}
		}
		catch(IllegalArgumentException exc){
			assert( board == null || !isValidPosition(X, Y, board));
			System.out.println("The item can not be placed on the board because the given position is invalid.");
			return;
		}
		catch(PositionTakenException exc){
			assert(!p.canHaveAsItemOnPosition(this));
			System.out.println("The item cannot be set to the new position because another item is already on that position.");
			return;
		}
		catch(IllegalStateException exc){
			System.out.println("This position does not exist");
		}
		}
	
	/**
	 * Uses setValidPosition using the X and Y coordinates of the given position.
	 * 
	 * @param position	
	 * 			The given position that has to be set.
	 * 
	 * @post	...
	 * 			|if(position == null)
	 * 			|	then new.getPosition() == null
	 * @effect	Else uses setValidPosition using the X and Y coordinates of the given position.
	 * 			|this.setValidPosition(position.getX(), position.getY(), position.getBoard())
	 */
	public void setValidPosition(Position position){
		try{
			if(position == null)
				this.position = null;
			else if(this.isTerminated())
				throw new TerminatedObjectException(this);
			else if(position.isTerminated())
				throw new TerminatedObjectException(position);
			else
				this.setValidPosition(position.getX(), position.getY(), position.getBoard());
		}
		catch(TerminatedObjectException exc){
			System.out.println("Either the position or this robot is terminated");
		}
	}
	
	/**
	 * Check whether this item has a proper position to which it is attached.
	 * 
	 * @return ...
	 * 			|result == (isValidPosition(this.getPosition())
	 *			|	&&( (this.getPosition() == null) 
	 *			|		|| (this.getPosition().getListOfItemsOnPosition().contains(this))))
	 */
	public boolean hasProperPosition(){
		return (isValidPosition(this.getPosition())	&&( (this.getPosition() == null) || (this.getPosition().getListOfItemsAtPosition().contains(this))));
	}

	
	/**
	 * Adds this robot to the list of robots.
	 * @post ...
	 * 			|board.getListOfRobots().contains(this)
	 * @throws IllegalArgumentException
	 * 			...
	 * 			|board == null
	 * @throws TerminatedObjectException
	 * 			...
	 *			|board.isTerminated() || this.isTerminated()
	 */
	public void addToBoard(Board board) throws IllegalArgumentException, TerminatedObjectException {
		try{
			if(board == null)
				throw new IllegalArgumentException();
			if(board.isTerminated())
				throw new TerminatedObjectException(board);
			if(this.isTerminated())
				throw new TerminatedObjectException(this);
			board.getListOfItemsOnBoard().add(this);
		}
		catch(IllegalArgumentException exc){
			System.out.println("The given board does not excist");
		}
		catch(TerminatedObjectException exc){
			System.out.println("This robot or the given board are terminated");
		}
	}

	/**
	 * 	Returns whether or not this item can be put at the given position at the given board. 
	 */
	public abstract boolean canPutItemAt(Position position);

	/**
	 * Checks whether a position on the given board and with the given X and Y coordinate exists. 
	 * 		If so, it returns the position, if not, it returns null.
	 * 		
	 * @param X
	 * 			The X coordinate that is part of the given position
	 * @param Y
	 * 			The Y coordinate that is part of the given position
	 * @param board
	 * 			The board the coordinate must exist on.
	 * @return ...
	 * 			|for each position in board.listOfPositions:
	 * 			|	if(position.getX() == X && position.getY() == Y)
	 * 			|		result == position
	 * 			
	 */
	public Position doesPositionAlreadyExist(long X, long Y, Board board){

		for(Position position: board.getListOfPositions()){
			if(position.getX() == X && position.getY() == Y){
				return position;
			}
		}
		return null;
	}
	
	/**
	 * Terminate this item.
	 * 
	 * @post ...
	 * 			|if(!isTerminated())
	 * 			|	then (! (new getPosition()).getListOfItemsOnPosition().contains(this))
	 * 
	 * @post ...
	 * 			|if(! isTerminated())
	 * 			|	then (! (new getPosition()).getBoard().listOfItems(this).contains(this))
	 * 
	 * @post ...
	 * 			|if(new getPosition().getListOfItemsOnPosition().isEmpty())
	 * 			|	then this.getPosition().terminate()
	 * 
	 * @post ...
	 * 			|this.new.getPosition() == null
	 * 
	 * @post This item is terminated.
	 * 			|new.isTerminated()
	 * 
	 */
	public void terminate() {	
		if(!isTerminated()){
			if(isOnABoard) {
				this.getPosition().listOfItemsOnPosition.remove(this);
				this.getPosition().getBoard().getListOfItemsOnBoard().remove(this);
				if(this.getPosition().listOfItemsOnPosition.isEmpty()) this.getPosition().terminate();
			}
			this.isOnABoard = false;
			this.position = null;
			this.isTerminated = true;
		}
	}
	
	
	public abstract void hitByLaser();
	
	/**
	 * Check whether this item has the given position as its position.
	 * 
	 * @param position
	 * 			The position to check
	 * (no return type in book. (p352) 	
	 */
	public boolean hasAsPosition(Position position){
		return this.getPosition() == position;
	}
	
	 /**
     * 
     */
    public abstract boolean hasAtleast(Object condition);
}